package com.cbs.persistencecache;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import com.cbs.database.Database;
import com.cbs.log.L;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by brochexu on 3/3/15.
 * Modified by BrocheXu on 3/20/15.
 */
public class LRUPersistenceCache implements PersistenceCache {
    private static final String TAG = LRUPersistenceCache.class.getName();
    private boolean isDebug = false;
    private final MyLinkedHashMap caches = new MyLinkedHashMap();
    private final CacheHandler cacheHandler = new CacheHandler();
    private int memoryCacheCapability = 10;
    private PersistenceThread thread;
    private ExecutorService executorService = Executors.newSingleThreadExecutor();
    private DataManager dataManager = null;

    public LRUPersistenceCache(int memoryCacheCapability, Database database) {
        this.memoryCacheCapability = memoryCacheCapability;
        this.dataManager = new DataManager(database);
        thread = new PersistenceThread();
        thread.start();
    }

    public LRUPersistenceCache setDebug(boolean isDebug) {
        this.isDebug = isDebug;
        return this;
    }

    public void setCache(final String scope, final String tag, final String key, final String value) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                synchronized (caches) {
                    String ttag = tag == null ? "" : tag;
                    String tkey = key == null ? "" : key;
                    String tvalue = value == null ? "" : value;
                    debug("Set", scope, ttag, tkey, tvalue);
                    CacheKey ckey = caches.getKey(scope, ttag, tkey);
                    if (ckey != null) {
                        caches.get(ckey).setValue(tvalue);
                    } else {
                        ckey = new CacheKey(scope, tag, key);
                        CacheValue cacheValue = new CacheValue(tvalue, true);
                        caches.put(ckey, cacheValue);
                    }
                }
            }
        };
        this.executorService.execute(thread);
    }

    public String getCache(String scope, String tag, String key) {
        String value;
        synchronized (caches) {
            tag = tag == null ? "" : tag;
            key = key == null ? "" : key;
            CacheKey ckey = caches.getKey(scope, tag, key);
            if (ckey != null) {
                value = caches.get(ckey).getValue();
            } else {
                ckey = new CacheKey(scope, tag, key);
                value = dataManager.getBuffer(scope, tag, key);
                CacheValue cvalue = new CacheValue(value, false);
                caches.put(ckey, cvalue);
            }
            debug("Get", scope, tag, key, value);
        }
        return value;
    }

    public void getCache(final String scope, final String tag, final String key, final CacheListener cacheListener) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                String ttag = tag == null ? "" : tag;
                String tkey = key == null ? "" : key;
                String value = getCache(scope, ttag, tkey);

                Bundle data = new Bundle();
                data.putString("key", key);
                data.putString("value", value);
                cacheHandler.obtainMessage(0, cacheListener).sendToTarget();
            }
        };
        this.executorService.execute(thread);
    }

    public void clearCache() {
        Thread thread = new Thread() {
            @Override
            public void run() {
                synchronized (caches) {
                    caches.clear();
                    dataManager.clear();
                    debug("ClearAll", "", "", "", "");
                }
            }
        };
        this.executorService.execute(thread);
    }

    public int getCacheSize() {
        return dataManager.getSize();
    }

    public void removeCache(final String scope) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                synchronized (caches) {
                    List<CacheKey> keys = caches.getKeys(scope);
                    for (CacheKey key : keys) {
                        caches.remove(key);
                    }
                    dataManager.removeBuffer(scope);
                    debug("Clear", scope, "", "", "");
                }
            }
        };
        this.executorService.execute(thread);
    }

    public void removeCache(final String scope, final String tag) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                synchronized (caches) {
                    String ttag = tag == null ? "" : tag;
                    List<CacheKey> keys = caches.getKeys(scope, ttag);
                    for (CacheKey key : keys) {
                        caches.remove(key);
                    }
                    dataManager.removeBuffer(scope, tag);
                    debug("Clear", scope, tag, "", "");
                }
            }
        };
        this.executorService.execute(thread);
    }

    public void removeCache(final String scope, final String tag, final String key) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                synchronized (caches) {
                    String ttag = tag == null ? "" : tag;
                    String tkey = key == null ? "" : key;
                    CacheKey ckey = caches.getKey(scope, ttag, tkey);
                    if (ckey != null) {
                        caches.remove(ckey);
                    }
                    dataManager.removeBuffer(scope, ttag, tkey);
                    debug("Clear", scope, ttag, tkey, "");
                }
            }
        };
        this.executorService.execute(thread);
    }


    private void debug(String method, String scope, String tag, String key, String value) {
        if (isDebug) {
            if (value.length() > 20) {
                value = value.substring(0, 20);
            }
            L.w(TAG, String.format("method: %s; scope: %s; tag: %s; key: %s\nvalue: %s", method, scope, tag, key, value));
        }
    }

    private static class CacheHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Bundle data = msg.getData();
            CacheListener cacheListener = (CacheListener) msg.obj;
            String key = data.getString("key");
            String value = data.getString("value");
            cacheListener.onCache(key, value);
        }
    }

    private class MyLinkedHashMap extends LinkedHashMap<CacheKey, CacheValue> {
        public CacheKey getKey(String scope, String tag, String key) {
            CacheKey tmpKey = new CacheKey(scope, tag, key);
            for (CacheKey ckey : keySet()) {
                if (ckey.equals(tmpKey)) {
                    return ckey;
                }
            }
            return null;
        }

        public List<CacheKey> getKeys(String scope, String tag) {
            List<CacheKey> keys = new ArrayList<>();
            for (CacheKey ckey : keySet()) {
                if (ckey.getScope().equals(scope) && ckey.tag.equals(tag)) {
                    keys.add(ckey);
                }
            }
            return keys;
        }

        public List<CacheKey> getKeys(String scope) {
            List<CacheKey> keys = new ArrayList<>();
            for (CacheKey ckey : keySet()) {
                if (ckey.getScope().equals(scope)) {
                    keys.add(ckey);
                }
            }
            return keys;
        }

        protected boolean removeEldestEntry(Map.Entry<CacheKey, CacheValue> eldest) {
            boolean flag = size() > LRUPersistenceCache.this.memoryCacheCapability;
            if (flag) {
                final CacheKey key = eldest.getKey();
                final CacheValue value = eldest.getValue();
                if (value.isChanged()) {
                    Thread thread = new Thread() {
                        @Override
                        public void run() {
                            dataManager.setBuffer(key.getScope(), key.getTag(), key.getKey(), value.getValue());
                        }
                    };
                    LRUPersistenceCache.this.executorService.execute(thread);
                }
                debug("RemoveMemory", key.getScope(), key.getTag(), key.getKey(), value.getValue());
            }
            return flag;
        }
    }

    private class CacheKey {
        private String scope;
        private String tag;
        private String key;

        public CacheKey(String scope, String tag, String key) {
            this.scope = scope;
            this.tag = tag;
            this.key = key;
        }

        public String getScope() {
            return scope;
        }

        public String getTag() {
            return tag;
        }

        public String getKey() {
            return key;
        }

        public boolean equals(CacheKey key) {
            return key.scope.equals(this.scope) && key.tag.equals(this.tag) && key.key.equals(this.key);
        }
    }

    private class CacheValue {
        private boolean isChanged = false;
        private String value = "";

        public CacheValue(String value, boolean isChanged) {
            this.value = value;
            this.isChanged = isChanged;
        }

        public void setValue(String value) {
            if (!this.value.equals(value)) {
                this.value = value;
                isChanged = true;
            }
        }

        public void resetChanged() {
            isChanged = false;
        }

        public boolean isChanged() {
            return isChanged;
        }

        public String getValue() {
            return value;
        }
    }

    private class PersistenceThread extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (caches) {
                    Iterator iterator = caches.entrySet().iterator();
                    while (iterator.hasNext()) {
                        Map.Entry<CacheKey, CacheValue> set = ((Map.Entry<CacheKey, CacheValue>) iterator.next());
                        CacheKey key = set.getKey();
                        CacheValue value = set.getValue();
                        if (value.isChanged()) {
                            dataManager.setBuffer(key.getScope(), key.getTag(), key.getKey(), value.getValue());
                            value.resetChanged();
                            debug("AutoSave", key.getScope(), key.getTag(), key.getKey(), value.getValue());
                        }

                    }
                }
                try {
                    sleep(10000);
                } catch (Exception e) {
                    L.w(TAG, "", e);
                }
            }
        }
    }
}
